/*
Script: Drag.js
  The base Drag Class. Can be used to drag and resize Elements using mouse events.

  License:
    MIT-style license.

  Authors:
    Valerio Proietti
    Tom Occhinno
    Jan Kassens
*/

var Drag = new Class({

  Extends: Drag,

  options: {/*
    onBeforeStart: $empty(thisElement),
    onStart: $empty(thisElement, event),
    onSnap: $empty(thisElement)
    onDrag: $empty(thisElement, event),
    onCancel: $empty(thisElement),
    onComplete: $empty(thisElement, event),*/
    snap: 6,
    unit: 'px',
    grid: false,
    style: true,
    limit: false,
    handle: false,
    invert: false,
    preventDefault: false,
    modifiers: {x: 'left', y: 'top'}
  },

  initialize: function(){
    var params = Array.link(arguments, {'options': Object.type, 'element': $defined});
    this.element = $(params.element);
    this.document = this.element.getDocument();
    this.setOptions(params.options || {});
    var htype = $type(this.options.handle);
    this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : $(this.options.handle)) || this.element;
    this.mouse = {'now': {}, 'pos': {}};
    this.value = {'start': {}, 'now': {}};

    this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';

    this.bound = {
      start: this.start.bind(this),
      check: this.check.bind(this),
      drag: this.drag.bind(this),
      stop: this.stop.bind(this),
      cancel: this.cancel.bind(this),
      eventStop: $lambda(false)
    };
    this.attach();
  },

  attach: function(){
    this.handles.addEvent('mousedown', this.bound.start);
    return this;
  },

  detach: function(){
    this.handles.removeEvent('mousedown', this.bound.start);
    return this;
  },

  start: function(event){
    if (this.options.preventDefault) event.preventDefault();
    this.mouse.start = event.page;
    this.fireEvent('beforeStart', this.element);
    var limit = this.options.limit;
    this.limit = {x: [], y: []};
    for (var z in this.options.modifiers){
      if (!this.options.modifiers[z]) continue;
      if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
      else this.value.now[z] = this.element[this.options.modifiers[z]];
      if (this.options.invert) this.value.now[z] *= -1;
      this.mouse.pos[z] = event.page[z] - this.value.now[z];
      if (limit && limit[z]){
        for (var i = 2; i--; i){
          if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
        }
      }
    }
    if ($type(this.options.grid) == 'number') this.options.grid = {x: this.options.grid, y: this.options.grid};
    this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
    this.document.addEvent(this.selection, this.bound.eventStop);
  },

  check: function(event){
    if (this.options.preventDefault) event.preventDefault();
    var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
    if (distance > this.options.snap){
      this.cancel();
      this.document.addEvents({
        mousemove: this.bound.drag,
        mouseup: this.bound.stop
      });
      this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
    }
  },

  drag: function(event){
    if (this.options.preventDefault) event.preventDefault();
    this.mouse.now = event.page;
    for (var z in this.options.modifiers){
      if (!this.options.modifiers[z]) continue;
      this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
      if (this.options.invert) this.value.now[z] *= -1;
      if (this.options.limit && this.limit[z]){
        if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
          this.value.now[z] = this.limit[z][1];
        } else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
          this.value.now[z] = this.limit[z][0];
        }
      }
      if (this.options.grid[z]) this.value.now[z] -= ((this.value.now[z] - this.limit[z][0]) % this.options.grid[z]);
      if (this.options.style) this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
      else this.element[this.options.modifiers[z]] = this.value.now[z];
    }
    this.fireEvent('drag', [this.element, event]);
  },

  cancel: function(event){
    this.document.removeEvent('mousemove', this.bound.check);
    this.document.removeEvent('mouseup', this.bound.cancel);
    if (event){
      this.document.removeEvent(this.selection, this.bound.eventStop);
      this.fireEvent('cancel', this.element);
    }
  },

  stop: function(event){
    this.document.removeEvent(this.selection, this.bound.eventStop);
    this.document.removeEvent('mousemove', this.bound.drag);
    this.document.removeEvent('mouseup', this.bound.stop);
    if (event) this.fireEvent('complete', [this.element, event]);
  }

});

Element.implement({

  makeResizable: function(options){
    var drag = new Drag(this, $merge({modifiers: {x: 'width', y: 'height'}}, options));
    this.store('resizer', drag);
    return drag.addEvent('drag', function(){
      this.fireEvent('resize', drag);
    }.bind(this));
  }
});